/*******************************************************************************
 * Copyright 2011 Andrea Guitto
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.andreaguitto.mobile.android.framework.base.orm;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteCursor;
import android.util.Log;

import com.andreaguitto.mobile.android.framework.base.annotations.AGColumn;
import com.andreaguitto.mobile.android.framework.base.annotations.AGColumnId;

public abstract class AGSQLiteDatabaseService<M extends AGSQLiteDatabaseMetadata, T extends AGSQLiteDatabaseObject> {

	private static final String getMethodPrefixSTD ="get";
	private static final String getMethodPrefixBOOL ="is";
	
	AGSQLiteDatabaseSession dbSession;
	M metadata;
	Class<T> objectClass;
	
	boolean verboseEnabled = false;
	
	public AGSQLiteDatabaseSession getDatabaseSession(){	return dbSession;	}
	public M getMetadata(){	return metadata;	}
	
	@SuppressWarnings("unchecked")
	public AGSQLiteDatabaseService(@SuppressWarnings("rawtypes") Class objectClass, M metadata, AGSQLiteDatabaseSession dbSession){
		this.metadata = metadata;
		this.dbSession = dbSession;
		this.objectClass = objectClass;
	}
	
    public long insert(ContentValues initialValues) 
    {
        return getDatabaseSession().getDatabase().insertOrThrow(getMetadata().getTableName(), null, initialValues);
    }	
    
    @SuppressWarnings("static-access")
    public boolean update(long rowId, ContentValues args) 
    {
        return getDatabaseSession().getDatabase().update(getMetadata().getTableName(), args, getMetadata().FIELD_ID + "=" + rowId, null) > 0;
    }     
    
    @SuppressWarnings("static-access")
	public boolean delete(long rowId) 
    {
        return getDatabaseSession().getDatabase().delete(getMetadata().getTableName(), getMetadata().FIELD_ID + "=" + rowId, null) > 0;
    }

    @SuppressWarnings({ "static-access", "unchecked" })
    public AGSQLiteCursor<T> getRow(long rowId) throws SQLException 
    {
        Cursor mCursor =
        	getDatabaseSession().getDatabase().query(true, getMetadata().getTableName(), getMetadata().getColumns(), 
                		getMetadata().FIELD_ID + "=" + rowId, 
                		null,
                		null, 
                		null, 
                		null, 
                		null);
        
        if (mCursor != null) {
            mCursor.moveToFirst();
        }
        
        return AGSQLiteCursor.getInstance(objectClass, (SQLiteCursor) mCursor);
    }    
    
    @SuppressWarnings("unchecked")
	public AGSQLiteCursor<T> getRows() 
    {
    	return AGSQLiteCursor.getInstance(objectClass, 
        	(SQLiteCursor) getDatabaseSession().getDatabase().query(getMetadata().getTableName(), getMetadata().getColumns(), 
                null, 
                null, 
                null, 
                null, 
                null)
        );
    }

    public T getObject(long rowId){
    	AGSQLiteCursor<T> select = getRow(rowId);
    	
    	if (select !=null && select.getCount()==1){
    		select.moveToFirst();
    		try{
    			T returnObject = select.getObject();
    			select.close();
    			
    			return returnObject;
    		}catch(Exception e){
    			e.printStackTrace();
    			select.close();
    			return null;
    		}
    	}
		return null;
    	
    }
    
    
    /**
     * Deletes all rows in entity
     * 
     * @param resetSQLiteSequence if true table restart id values sequence from 1
     */
	public void clearTable(boolean resetSQLiteSequence){
		getDatabaseSession().getDatabase().delete(getMetadata().getTableName(), null, null);
		if(resetSQLiteSequence){
			String cancella = "delete from sqlite_sequence where name='"+getMetadata().getTableName()+"'";
			getDatabaseSession().getDatabase().execSQL(cancella);
			cancella = null;
		}
	}	
	
    public void delete(T object){
    	delete(object.getId());
    }    
    
    public long saveOrInsert(T object) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		
    	ContentValues initialValues = new ContentValues();
		boolean isGetMethod;
		boolean isBoolMethod;
		
		boolean isUpdating = object.getId()>0; 
		
		for(Method getMethod : object.getClass().getDeclaredMethods()){
			
			isGetMethod = false;
			isBoolMethod = false;

			isBoolMethod= getMethod.getName().startsWith(getMethodPrefixBOOL);
			isGetMethod = getMethod.getName().startsWith(getMethodPrefixSTD) || isBoolMethod;
			
			if(isGetMethod){
				String name = null;
				
				if(isBoolMethod)
					name = getMethod.getName().substring(2, getMethod.getName().length());
				else
					name = getMethod.getName().substring(3, getMethod.getName().length());
				
				if(name!=null){
					Annotation[] annotations = getMethod.getDeclaredAnnotations();
			
					for(Annotation annotation : annotations){
					    if(annotation instanceof AGColumn){
					    	AGColumn myAnnotation = (AGColumn) annotation;

					    	Object objReceivedFromGetCall = (getMethod.invoke(object, (Object[]) null));
					    	
					    	if(myAnnotation.name()!=null && myAnnotation.name().trim().length()>0){
						    	if(objReceivedFromGetCall!=null){
						    		if(verboseEnabled) Log.d("saveOrInsert", "Class: "+object.getClass().getName()+" ; Name: "+myAnnotation.name()+" ; Value: "+objReceivedFromGetCall.toString());
						    		initialValues.put(myAnnotation.name(), objReceivedFromGetCall.toString());
						    	}else{
						    		if(verboseEnabled) Log.d("saveOrInsert", "Class: "+object.getClass().getName()+" ; Name: "+myAnnotation.name()+" ; Value: null");
						    		initialValues.put(myAnnotation.name(), "null");
						    	}
					    	}
					    		
					    	objReceivedFromGetCall = null;
					    	
					    }else if(annotation instanceof AGColumnId){
					    	AGColumnId myAnnotation = (AGColumnId) annotation;
					    	
					    	if(!myAnnotation.AutoGenerated() || isUpdating){
					    		Object objReceivedFromGetCall = (getMethod.invoke(object, (Object[]) null));
						    		
						    	if(myAnnotation.name()!=null && myAnnotation.name().trim().length()>0){
							    	if(objReceivedFromGetCall!=null){
							    		if(verboseEnabled) Log.d("saveOrInsert", "Class: "+object.getClass().getName()+" ; Name: "+myAnnotation.name()+" ; Value: "+objReceivedFromGetCall.toString());
							    		initialValues.put(myAnnotation.name(), objReceivedFromGetCall.toString());
							    	}else{
							    		if(verboseEnabled) Log.d("saveOrInsert", "Class: "+object.getClass().getName()+" ; Name: "+myAnnotation.name()+" ; Value: null");
							    		initialValues.put(myAnnotation.name(), "null");
							    	}
						    	}
						    	objReceivedFromGetCall = null;					    		
					    	}
					    }
					}
					
					annotations = null;
				}				
			}
		}
		
		long newRowId=-1;
		if(isUpdating){
			newRowId = object.getId();
			if(update(object.getId(), initialValues)){
				if(verboseEnabled) Log.d("saveOrInsert", "update "+object.getId()+" !");
			}
		}else{
			if((newRowId=insert(initialValues))>-1){
				if(verboseEnabled) Log.d("saveOrInsert", "insert "+newRowId+ " !");
				object = getObject(newRowId);
			}
		}
		
    	return newRowId;
    }
	public boolean isVerboseEnabled() {
		return verboseEnabled;
	}
	public void setVerboseEnabled(boolean verboseEnabled) {
		this.verboseEnabled = verboseEnabled;
	}      
    
    /**
     *  Transform a cursor into an object.
     * 
     * @param autocloseCursor
     * @param select
     * @return
     */
/*    
    public T getObject(AGSQLiteCursor<T> select, boolean autocloseCursor){
    	T objectToReturn = null;
    	try{
	    	if(select!=null)
	    		objectToReturn = select.getObject();
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    	
    	if(autocloseCursor){
			if(!select.isClosed())
				select.close();    	
    	}
    	return objectToReturn;
    } 
*/    
    /**
     *  Transform a cursor into an object.
     * 
     * @param autocloseCursor
     * @param select
     * @return
     */
/*
    public Collection<T> getCollection(AGSQLiteCursor<T> select, boolean autocloseCursor){
    	Collection<T> objectToReturn = select.getCollection();
    	
    	if(autocloseCursor){
	    	if(!select.isClosed())
	    		select.close();
    	}
    	return objectToReturn;
    }
*/    
}
